package service

import (
	"context"
	v1 "geekbang-go-homework/week04/api/order/v1"
	"geekbang-go-homework/week04/internal/domain"
	"geekbang-go-homework/week04/internal/pkg/copier"

	"google.golang.org/protobuf/types/known/timestamppb"

	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/types/known/emptypb"
)

type OrderService struct {
	v1.UnimplementedOrderApiServer
	uc domain.OrderUsecase
}

func NewOrderService(uc domain.OrderUsecase) v1.OrderApiServer {
	return &OrderService{
		uc: uc,
	}
}

// ListOrders get order list.
func (s *OrderService) ListOrders(ctx context.Context, req *v1.ListOrdersRequest) (*v1.ListOrdersResponse, error) {
	if req.PageNo < 1 || req.PageSize > 100 {
		return nil, status.Errorf(codes.InvalidArgument, "specified an invalid argument")
	}
	so := &domain.SearchOrderList{}
	err := copier.Copy(so, req)
	if err != nil {
		return nil, err
	}
	list, p, err := s.uc.ListOrders(ctx, so)
	if err != nil {
		return nil, err
	}
	re := make([]*v1.Order, len(list))
	for k, v := range list {
		re[k], err = s.doConvDto(v)
		if err != nil {
			return nil, err
		}
	}
	return &v1.ListOrdersResponse{
		Orders: re,
		Pagination: &v1.Pagination{
			Total:    p.Total,
			PageNo:   p.PageNo,
			PageSize: p.PageSize,
		},
	}, nil
}

// GetOrder get order by id.
func (s *OrderService) GetOrder(ctx context.Context, req *v1.GetOrderRequest) (*v1.Order, error) {
	if req.GetId() < 1 {
		return nil, status.Errorf(codes.InvalidArgument, "specified an invalid argument")
	}
	do, err := s.uc.GetOrder(ctx, req.GetId())
	if err != nil {
		return nil, err
	}
	return s.doConvDto(do)
}

// CreateOrder create order.
func (s *OrderService) CreateOrder(ctx context.Context, req *v1.CreateOrderRequest) (*v1.Order, error) {
	if req.GetUserId() < 1 || req.MerchantId < 1 || len(req.GetItems()) == 0 {
		return nil, status.Errorf(codes.InvalidArgument, "specified an invalid argument")
	}
	do, err := s.dtoConvDo(req)
	if err != nil {
		return nil, err
	}
	do, err = s.uc.CreateOrder(ctx, do)
	if err != nil {
		return nil, err
	}
	return s.doConvDto(do)
}

// UpdateOrder update order.
func (s *OrderService) UpdateOrder(ctx context.Context, req *v1.UpdateOrderRequest) (*v1.Order, error) {
	if !req.GetUpdateMask().IsValid(req.GetOrder()) ||
		req.GetOrder() == nil ||
		len(req.GetUpdateMask().GetPaths()) == 0 {
		return nil, status.Errorf(codes.InvalidArgument, "specified an invalid argument")
	}
	do, err := s.dtoConvDo(req.Order)
	if err != nil {
		return nil, err
	}
	do, err = s.uc.UpdateOrder(ctx, do, req.GetUpdateMask().GetPaths())
	if err != nil {
		return nil, err
	}
	return s.doConvDto(do)
}

// DeleteOrder delete order.
func (s *OrderService) DeleteOrder(ctx context.Context, req *v1.DeleteOrderRequest) (*emptypb.Empty, error) {
	if req.GetId() < 1 {
		return nil, status.Errorf(codes.InvalidArgument, "specified an invalid argument")
	}
	return nil, s.uc.DeleteOrder(ctx, req.GetId())
}

func (s *OrderService) dtoConvDo(from interface{}) (do *domain.Order, err error) {
	do = &domain.Order{}
	if err = copier.Copy(do, from); err != nil {
		return
	}
	if v, ok := from.(interface {
		GetItems() []*v1.OrderItem
	}); ok {
		do.Items = make([]*domain.OrderItem, len(v.GetItems()))
		for k, item := range v.GetItems() {
			do.Items[k] = &domain.OrderItem{}
			if err = copier.Copy(do.Items[k], item); err != nil {
				return nil, err
			}
		}
	}
	return
}

func (s *OrderService) doConvDto(from *domain.Order) (dto *v1.Order, err error) {
	if from == nil {
		return
	}
	dto = &v1.Order{}
	if err = copier.Copy(dto, from); err != nil {
		return
	}
	dto.CreateTime = timestamppb.New(from.CreateTime)
	dto.UpdateTime = timestamppb.New(from.UpdateTime)
	dto.Items = make([]*v1.OrderItem, len(from.Items))
	for k, item := range from.Items {
		dto.Items[k] = &v1.OrderItem{}
		if err = copier.Copy(dto.Items[k], item); err != nil {
			return nil, err
		}
	}
	return
}
